#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// An environment is a collection of hashtables that maps symbols 
    /// to values, types, and properties. In addition, environments can 
    /// be nested to support local variables.
	/// </summary>
	public class Environment
    {
        #region Private members        
        private const int CAPACITY = 10;

        private Dictionary<Symbol, Object> symboltable = new Dictionary<Symbol, Object>(CAPACITY);
        private Dictionary<Symbol, Dictionary<Symbol, Object>> symbolprops = 
                                                        new Dictionary<Symbol, Dictionary<Symbol, object>>(CAPACITY);

        private Dictionary<Symbol, Type> symboltypes = new Dictionary<Symbol, Type>(CAPACITY);

		// Maintain a reference to a previous environment to allow nesting
		// of environments, thus supporting local variables and recursion
		private Environment previousEnvironment;

        /// <summary>
        /// Returns TRUE if the environment is a root-level environment, i.e. it doesn't wrap another environment
        /// Returns FALSE if the environment wraps another environment
        /// </summary>
        public bool IsRootEnvironment { get { return previousEnvironment == null; } }
        #endregion


        #region Properties
        /// <summary>
        /// String Indexer. Looks up the value bound to the symbol with 
        /// the specified name. If the value is a Yarr.Closure, it is wrapped
        /// with a Yarr.Fn delegate so that it may be invoked by caller
        /// </summary>
        /// <param name="index">A symbol name</param>
        /// <returns>The value bound to the symbol</returns>
        public object this[string index]
        {
            get { return this[Symbol.FromName(index)]; }
            set { Assign(Symbol.FromName(index), value); }
        }

        /// <summary>
        /// Symbol Indexer. Looks up the value bound to the specified symbol.
        /// If the value is a Yarr.Closure, it is wrapped in a Yarr.Fn delegate 
        /// so that it may be invoked by caller
        /// </summary>
        /// <param name="index">A Symbol</param>
        /// <returns>The value bound to the symbol</returns>
        public object this[Symbol index]
        {
            get
            {
                Object o = GetValue(index);
                if (o is Closure)
                    o = FunctionBinding.Bind((Closure)o);

                return o;
            }

            set { Assign(index, value); }
        }
        #endregion


        #region Constructors & initialization
        /// <summary>
        /// Creates a new Yarr environment
        /// </summary>
        /// <returns>An environment</returns>
        public static Environment MakeEnvironment()
        {
            return new Environment();
        }

        /// <summary>
        /// Create a local environment
        /// </summary>
        /// <param name="env">An existing environment</param>
        /// <returns>A local environment wrapping the existing environment</returns>
        public static Environment MakeEnvironment(Environment env)
        {
            return new Environment(env);
        }


        private Environment()
        {
            InitialiseBindings();
            AssignLocal(Symbol.ENVIRONMENT, this);
        }

        private Environment(Environment environment)
        {
            this.previousEnvironment = environment;
            AssignLocal(Symbol.ENVIRONMENT, this);
        }


        /// <summary>
        /// Reset the environment, unbinding all symbols
        /// </summary>
        public void GlobalReset()
        {
            lock (symboltable)
            {
                if (symboltable.Count > 0)
                    symboltable = new Dictionary<Symbol, Object>(CAPACITY);
            }

            lock (symbolprops)
            {
                if (symbolprops.Count > 0)
                    symbolprops = new Dictionary<Symbol, Dictionary<Symbol, object>>(CAPACITY);
            }

            lock (symboltypes)
            {
                if (symboltypes.Count > 0)
                    symboltypes = new Dictionary<Symbol, Type>(CAPACITY);
            }

            if (previousEnvironment == null)
                InitialiseBindings();
        }


        private void InitialiseBindings()
        {
            // The read table
            this[Symbol.READTABLE] = new ReadTable();

            ImportClass(typeof(Reader));        // The built-in reader macros
            ImportClass(typeof(SetfMethods));   // The built-in setf methods
            ImportClass(typeof(SpecialForms));  // The built-in special forms
            ImportClass(typeof(Functions));     // The built-in functions
            ImportClass(typeof(Macros));        // The built-in macros
        }
        #endregion


        #region Public methods
        /// <summary>
        /// Asssigns value to a local variable symbol in this
        /// local environment (irrespective of whether symbol
        /// is defined in any parent environments).
        /// </summary>
        /// <param name="symbol">The name of the symbol to bind</param>
        /// <param name="value">The value to bind the symbol to</param>
        /// <returns>The value</returns>
        public Object AssignLocal(string symbol, Object value)
        {
            return AssignLocal(Symbol.FromName(symbol), value);
        }

        /// <summary>
        /// Asssigns value to a local variable symbol in this
        /// local environment (irrespective of whether symbol
        /// is defined in any parent environments).
        /// </summary>
        /// <param name="package">The name of the package in which to find or create the symbol</param>
        /// <param name="symbol">The name of the symbol to bind</param>
        /// <param name="value">The value to bind the symbol to</param>
        /// <returns></returns>
        public Object AssignLocal(string package, string symbol, Object value)
        {
            return AssignLocal(Symbol.FromName(package, symbol), value);
        }

        /// <summary>
        /// Asssigns value to a local variable symbol in this
        /// local environment (irrespective of whether symbol
        /// is defined in any parent environments).
        /// </summary>
        /// <param name="symbol">The symbol to bind</param>
        /// <param name="value">The value to bind it to</param>
        /// <returns>The value</returns>
        public Object AssignLocal(Symbol symbol, Object value)
        {
            if (symbol.PackageName == Package.KEYWORD)
                throw new YarrException("Assign: Cannot bind values to keywords");

            value = Primitives.ValueOf(value);

            bool containsKey;
            lock (symboltypes)
            {
                containsKey = symboltypes.ContainsKey(symbol);
            }

            if (containsKey)
            {
                Type symbolType;
                lock (symboltypes)
                {
                    symbolType = symboltypes[symbol];
                }

                if (!Primitives.IsTypeOf(symbolType, value))
                {
                    if (!Conversions.TryImplicitConversion(value, symbolType, out value))
                        throw new YarrException(
                            String.Format(CultureInfo.InvariantCulture,
                                        "Assign: Expecting value of type {0} for {1}",
                                        symbolType, symbol));
                }
            }

            lock (symboltable)
            {
                symboltable[symbol] = value;
            }

            return value;
        }


        /// <summary>
        /// Bind a function delegate to a symbol
        /// </summary>
        /// <param name="name">The name of the symbol</param>
        /// <param name="func">The function delegate</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindMethod(string name, Func<Environment, Object> func, string parameterList)
        {
            BindMethod(Symbol.FromName(name), func, parameterList);
        }

        /// <summary>
        /// Bind a function delegate to a symbol
        /// </summary>
        /// <param name="package">The name of the package in which to find or create the symbol</param>
        /// <param name="name">The name of the symbol</param>
        /// <param name="func">The function delegate</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindMethod(string package, string name, Func<Environment, Object> func, string parameterList)
        {
            BindMethod(Symbol.FromName(package, name), func, parameterList);
        }

        /// <summary>
        /// Bind a function delegate to a symbol
        /// </summary>
        /// <param name="name">The symbol</param>
        /// <param name="func">The function delegate</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindMethod(Symbol name, Func<Environment, Object> func, string parameterList)
        {
            Cons args = (Cons)Primitives.ParseString(parameterList, this);

            Assign(name, new Closure(args, func, this));
        }


        /// <summary>
        /// Assigns a setf method
        /// </summary>
        /// <param name="symbol">The name of the symbol to bind to the setf method</param>
        /// <param name="func">The setf method</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindSetf(String symbol, Func<Environment, object> func, string parameterList)
        {
            BindSetf(Symbol.FromName(symbol), func, parameterList);
        }

        /// <summary>
        /// Assigns a setf method
        /// </summary>
        /// <param name="package">The name of the pacakge in which to find or create the symbol</param>
        /// <param name="symbol">The name of the symbol to bind to the setf method</param>
        /// <param name="func">The setf method</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindSetf(String package, String name, Func<Environment, object> func, string parameterList)
        {
            BindSetf(Symbol.FromName(package, name), func, parameterList);
        }

        /// <summary>
        /// Assigns a setf method
        /// </summary>
        /// <param name="symbol">The symbol to bind to the setf method</param>
        /// <param name="f">The setf method</param>
        /// <param name="parameterList">The parameter list for the function</param>
        public void BindSetf(Symbol symbol, Func<Environment, object> f, string parameterList)
        {
            Cons args = (Cons)Primitives.ParseString(parameterList, this);

            BindSetf(symbol, new Closure(args, f, this));
        }

        
        /// <summary>
        /// Determines whether the environment contains a value bound to the specified symbol
        /// </summary>
        /// <param name="symbol">The nme of a symbol</param>
        /// <returns>True if a value is bound to the symbol in the environment, false otherwise</returns>
        public bool Contains(string symbol)
        {
            return Contains(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Determines whether the environment contains a value bound to the specified symbol
        /// </summary>
        /// <param name="package">The name of the package in which to find or create the symbol</param>
        /// <param name="symbol">The nme of a symbol</param>
        /// <returns>True if a value is bound to the symbol in the environment, false otherwise</returns>
        public bool Contains(string package, string name)
        {
            return Contains(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Determines whether the environment contains a definition for
        /// a variable with the given symbol
        /// </summary>
        /// <param name="symbol">A Symbol</param>
        /// <returns>True if the symbol is in the environment, false otherwise</returns>
        public bool Contains(Symbol symbol)
        {
            bool containsKey;

            lock (symboltable)
            {
                containsKey = symboltable.ContainsKey(symbol);
            }

            if (containsKey)
                return true;

            if (previousEnvironment != null)
                return previousEnvironment.Contains(symbol);

            return false;
        }


        /// <summary>
        /// Declare a symbol in this environment to be of a specified type
        /// </summary>
        /// <param name="symbol">The name of the symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        public Object Declare(string symbol, Type type)
        {
            return Declare(Symbol.FromName(symbol), type, null);
        }

        /// <summary>
        /// Declare a symbol in this environment to be of a specified type
        /// </summary>
        /// <param name="package">The name of the package in which to find or create the symbol</param>
        /// <param name="name">The name of the symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        /// <returns></returns>
        public Object Declare(string package, string name, Type type)
        {
            return Declare(Symbol.FromName(package, name), type, null);
        }

        /// <summary>
        /// Declare a symbol in this environment to be of a specified type
        /// </summary>
        /// <param name="symbol">The symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        public Object Declare(Symbol symbol, Type type)
        {
            Object value = null;

            bool containsKey;
            lock (symboltable)
            {
                containsKey = symboltable.ContainsKey(symbol);
            }

            if (!containsKey)
            {
                lock (symboltypes)
                {
                    symboltypes[symbol] = type;
                }

                if (!type.IsClass)
                    AssignLocal(symbol, value = Runtime.MakeInstance(type, null));
            }

            return value;
        }

        /// <summary>
        /// Declare a symbol in this environment to be of a specified type,
        /// and provide an initial value
        /// </summary>
        /// <param name="symbol">The name of the symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        /// <param name="value">The initial value for the symbol</param>
        public Object Declare(string symbol, Type type, Object value)
        {
            return Declare(Symbol.FromName(symbol), type, value);
        }

        /// <summary>
        /// Declare a symbol in this environment to be of a specified type,
        /// and provide an initial value
        /// </summary>
        /// <param name="package">The name of the package in which to find or create the symbol</param>
        /// <param name="name">The name of the symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        /// <param name="value">The initial value for the symbol</param>
        /// <returns></returns>
        public Object Declare(string package, string name, Type type, Object value)
        {
            return Declare(Symbol.FromName(package, name), type, value);
        }

        /// <summary>
        /// Declare a symbol in this environment to be of a specified type,
        /// and provide an initial value
        /// </summary>
        /// <param name="symbol">The symbol to declare</param>
        /// <param name="type">The declared type for the symbol</param>
        /// <param name="value">The initial value for the symbol</param>
        public Object Declare(Symbol symbol, Type type, Object value)
        {
            bool containsKey;
            lock (symboltable)
            {
                containsKey = symboltable.ContainsKey(symbol);
            }

            if (!containsKey)
            {
                lock (symboltypes)
                {
                    symboltypes[symbol] = type;
                }

                if (value == null && !type.IsClass)
                    value = Runtime.MakeInstance(type, null);
            }

            return AssignLocal(symbol, value);
        }


        /// <summary>
        /// Returns the contents of the environment as a string suitable for use
        /// in a debugger or IDE.
        /// </summary>
        /// <returns>A string representation of the environment</returns>
        public string DumpContents()
        {
            StringBuilder stringBuilder = new StringBuilder();

            bool containsKey;

            lock (symboltable)
            {
                foreach (Symbol key in symboltable.Keys)
                {
                    lock (symboltypes)
                    {
                        stringBuilder.AppendFormat("{0} :: {1}\r\n", key.FullName, 
                                                    symboltypes.ContainsKey(key) ? symboltypes[key].ToString() : "(untyped)");
                    }

                    stringBuilder.AppendFormat("\tvalue := [{0}]\r\n", symboltable[key] == null ? "null" : symboltable[key]);

                    lock (symbolprops)
                    {
                        containsKey = symbolprops.ContainsKey(key);
                    }

                    if (containsKey)
                    {
                        stringBuilder.Append("\tproperties:\r\n");

                        lock (symbolprops[key])
                        {
                            foreach (Symbol prop in symbolprops[key].Keys)
                            {
                                stringBuilder.AppendFormat("\t\t name := {0}\r\n\t\tvalue := \r\n\t\t\t{1}\r\n",
                                                            prop.FullName, symbolprops[key][prop]);
                            }
                        }
                    }

                    if (previousEnvironment != null)
                        stringBuilder.Append(previousEnvironment.DumpContents());
                }
            }

            return stringBuilder.ToString();
        }


        /// <summary>
        /// Obtain an Action<...> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetAction(String symbolName)
        {
            return GetAction(Symbol.FromName(symbolName));
        }

        /// <summary>
        /// Obtain an Action<...> from a Yarr closure
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetAction(String package, String name)
        {
            return GetAction(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Obtain a Action<...> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">A symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetAction(Symbol symbol)
        {
            return GetClosure(symbol).Action;
        }

        /// <summary>
        /// Obtain a Func<...> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetFunc(String symbolName)
        {
            return GetFunc(Symbol.FromName(symbolName));
        }

        /// <summary>
        /// Obtain a Func<...> from a Yarr closure
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetFunc(String package, String name)
        {
            return GetFunc(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Obtain a Func<...> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">A symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetFunc(Symbol symbol)
        {
            return GetClosure(symbol).Func;
        }

        /// <summary>
        /// Obtain a Predicate<T> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetPredicate(String symbolName)
        {
            return GetPredicate(Symbol.FromName(symbolName));
        }

        /// <summary>
        /// Obtain a Predicate<T> from a Yarr closure
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of a symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetPredicate(String package, String name)
        {
            return GetPredicate(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Obtain a Predicate<T> from a Yarr closure
        /// </summary>
        /// <param name="symbolName">A symbol bound to a closure</param>
        /// <returns>A delegate for the closure</returns>
        public Delegate GetPredicate(Symbol symbol)
        {
            return GetClosure(symbol).Predicate;
        }


        /// <summary>
        /// Get a symbol's property value
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="prop">The property</param>
        /// <returns>The value</returns>
        public Object GetProperty(Symbol symbol, Symbol prop)
        {
            Environment env = this;

            bool containsKey;
            while (env != null)
            {
                lock (env.symbolprops)
                {
                    containsKey = env.symbolprops.ContainsKey(symbol);
                }

                if (containsKey)
                {
                    lock (env.symbolprops[symbol])
                    {
                        containsKey = env.symbolprops[symbol].ContainsKey(prop);
                    }

                    if (containsKey)
                        return env.symbolprops[symbol][prop];
                }

                env = env.previousEnvironment;
            }

            return null;
        }


        /// <summary>
        /// Environment Lookup
        /// </summary>
        /// <param name="symbol">The symbol to look up</param>
        /// <returns>The value bound to the symbol in this environment</returns>
        public object GetValue(string symbol)
        {
            return GetValue(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Environment Lookup
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of the symbol to look up</param>
        /// <returns>The value bound to the symbol in this environment</returns>
        public object GetValue(string package, string name)
        {
            return GetValue(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Environment Lookup
        /// </summary>
        /// <param name="symbol">The symbol to look up</param>
        /// <returns>The value bound to the symbol in this environment</returns>
        public object GetValue(Symbol symbol)
        {
            Environment env = GetEnvironment(symbol, false);

            if (env == null)
                return null;

            lock (env.symboltable)
            {
                return env.symboltable[symbol];
            }
        }


        /// <summary>
        /// Import every class in the specified assembly
        /// </summary>
        /// <param name="filename">The filename of the assembly to import</param>
        public Assembly ImportAssembly(string assemblyName)
        {
            Assembly asm = null;

            try
            {
                asm = Runtime.LoadAssembly(assemblyName);
                if (asm != null)
                    foreach (Type t in asm.GetTypes())
                        ImportClass(t);
            }
            catch (YarrException)
            {
                throw;
            }
            catch (Exception e)
            {
                YarrException ex = new YarrException(
                                    String.Format(CultureInfo.CurrentCulture, 
                                            "ImportAssembly: Error importing {0}", 
                                                    assemblyName), e);
                throw ex;
            }

            return asm;
        }

        /// <summary>
        /// Import .NET methods from the specified class and bind them into the environment
        /// </summary>
        /// <param name="type">The class to import</param>
        public void ImportClass(Type type)
        {
            if (type == null)
                throw new YarrException("ImportClass: type is null");

            Package previousPackage = Symbol.CurrentPackage;

            try
            {
                string pkgName;

                PackageAttribute[] pkg = (PackageAttribute[])type.GetCustomAttributes(typeof(PackageAttribute), false);
                if (pkg == null || pkg.Length == 0)
                    pkgName = null;
                else if (pkg.Length == 1)
                    pkgName = pkg[0].PackageName;
                else
                    throw new YarrException(String.Format(CultureInfo.CurrentCulture,
                                                "ImportClass: More than one package specified for class {0}", type));

                if (!String.IsNullOrEmpty(pkgName))
                    Symbol.CurrentPackage = Package.MakePackage(pkgName);

                ReadTable readTable = (ReadTable)this.GetValue(Symbol.READTABLE);

                // Functions, SETF forms, Special forms, and reader macros are methods
                foreach (MethodInfo method in type.GetMethods(BindingFlags.Public |
                                                              BindingFlags.NonPublic |
                                                              BindingFlags.Static |
                                                              BindingFlags.DeclaredOnly |
                                                              BindingFlags.FlattenHierarchy))
                {
                    Func<Environment, object> fun = null;
                    SpecialForm frm = null;
                    ReaderMacro rmac = null;
                    DispatchReaderMacro dispmac = null;

                    foreach (Attribute attr in Attribute.GetCustomAttributes(method))
                    {
                        if (attr.GetType() == typeof(FunctionAttribute))
                        {
                            FunctionAttribute fa = attr as FunctionAttribute;
                            try
                            {
                                if (fun == null)
                                    fun = (Func<Environment, object>)
                                            Delegate.CreateDelegate(
                                                typeof(Func<Environment, object>), method);
                            }
                            catch (Exception e)
                            {
                                throw new YarrException(
                                    String.Format(CultureInfo.CurrentCulture,
                                            "ImportClass: Error creating delegate for {0}",
                                                    method.ToString()), e);
                            }

                            if (fa.binding != null && fun != null)
                            {
                                Cons signature = (Cons)Primitives.ParseString(fa.signature, this);
                                Closure cl = new Closure(signature, fun, this);
                                string[] names = fa.binding.Split(new char[] { ';' });

                                foreach (string name in names)
                                {
                                    if (!name.Contains(":"))
                                        Symbol.CurrentPackage.Shadow(name);
                                    this.Declare(Symbol.FromName(name), typeof(Closure), cl);
                                }
                            }
                        }

                        else if (attr.GetType() == typeof(SpecialFormAttribute))
                        {
                            SpecialFormAttribute sfa = attr as SpecialFormAttribute;
                            try
                            {
                                if (frm == null)
                                    frm = (SpecialForm)
                                            Delegate.CreateDelegate(typeof(SpecialForm), method);
                            }
                            catch (Exception e)
                            {
                                throw new YarrException(
                                            String.Format(CultureInfo.CurrentCulture,
                                                    "ImportClass: Error creating delegate for {0}",
                                                            method.ToString()), e);
                            }

                            if (sfa.binding != null && frm != null)
                            {
                                string[] names = sfa.binding.Split(new char[] { ';' });

                                foreach (string name in names)
                                {
                                    if (!name.Contains(":"))
                                        Symbol.CurrentPackage.Shadow(name);
                                    this.Declare(Symbol.FromName(name), typeof(SpecialForm), frm);
                                }
                            }
                        }

                        else if (attr.GetType() == typeof(SetfAttribute))
                        {
                            SetfAttribute stf = attr as SetfAttribute;
                            try
                            {
                                if (fun == null)
                                    fun = (Func<Environment, object>)
                                                Delegate.CreateDelegate(
                                                    typeof(Func<Environment, object>), method);
                            }
                            catch (Exception e)
                            {
                                throw new YarrException(
                                            String.Format(CultureInfo.CurrentCulture,
                                                    "ImportClass: Error creating delegate for {0}",
                                                            method.ToString()), e);
                            }

                            if (fun != null && stf.binding != null)
                            {
                                Cons signature = (Cons)Primitives.ParseString(stf.signature, this);
                                Closure cl = new Closure(signature, fun, this);
                                string[] names = stf.binding.Split(new char[] { ';' });

                                foreach (string name in names)
                                    SetProperty(Symbol.FromName(name), Symbol.SETF_DISP, cl);
                            }
                        }

                        else if (attr.GetType() == typeof(ReaderMacroAttribute))
                        {
                            try
                            {
                                if (rmac == null)
                                    rmac = (ReaderMacro)
                                            Delegate.CreateDelegate(typeof(ReaderMacro), method);
                            }
                            catch (Exception e)
                            {
                                throw new YarrException(
                                        String.Format(CultureInfo.CurrentCulture,
                                                "ImportClass: Error creating delegate for {0}",
                                                        method.ToString()), e);
                            }
                            if (rmac != null)
                                readTable.SetMacroCharacter(
                                        (Char)((ReaderMacroAttribute)attr).Binding, rmac);
                        }

                        else if (attr.GetType() == typeof(DispatchReaderMacroAttribute))
                        {
                            try
                            {
                                if (dispmac == null)
                                    dispmac = (DispatchReaderMacro)
                                        Delegate.CreateDelegate(typeof(DispatchReaderMacro), method);
                            }
                            catch (Exception e)
                            {
                                throw new YarrException(
                                        String.Format(CultureInfo.CurrentCulture,
                                                "ImportClass: Error creating delegate for {0}",
                                                        method.ToString()), e);
                            }
                            if (dispmac != null)
                                readTable.SetDispatchCharacter(
                                    (Char)((DispatchReaderMacroAttribute)attr).Binding,
                                    (Char)((DispatchReaderMacroAttribute)attr).SubChar,
                                    (Object)dispmac);
                        }
                    }
                }

                // Evaluated forms are static strings

                foreach (FieldInfo field in type.GetFields(BindingFlags.Public |
                                                           BindingFlags.NonPublic |
                                                           BindingFlags.Static |
                                                           BindingFlags.FlattenHierarchy |
                                                           BindingFlags.DeclaredOnly))
                {
                    object eval = null;
                    object value = null;

                    foreach (Attribute attr in Attribute.GetCustomAttributes(field))
                    {
                        if (attr.GetType() == typeof(EvalAttribute))
                        {
                            if (eval == null)
                                eval = field.GetValue(null);

                            if (eval != null && value == null)
                                value = (eval is String) ? Runtime.EvalString((String)eval, this)
                                                         : Runtime.Eval(eval, this);

                            if (!String.IsNullOrEmpty(((EvalAttribute)attr).binding))
                            {
                                string name = ((EvalAttribute)attr).binding;
                                if (!name.Contains(":"))
                                    Symbol.CurrentPackage.Shadow(name);
                                this.Declare(Symbol.FromName(name), Primitives.TypeOf(value), value);
                            }
                        }
                    }
                }
            }
            catch (YarrException)
            {
                throw;
            }
            catch (Exception e)
            {
                YarrException ex = new YarrException(
                                    String.Format(CultureInfo.CurrentCulture,
                                            "ImportClass: Error importing {0}", type), e);
                throw ex;
            }
            finally
            {
                Symbol.CurrentPackage = previousPackage;
            }
        }


        /// <summary>
        /// Determine if a symbol has been declared in the local environment
        /// </summary>
        /// <param name="symbol">The name of the symbol</param>
        /// <returns>
        /// True if the symbol is either delcared or bound in the local environment,
        /// false otherwise
        /// </returns>
        public bool IsDeclared(string symbol)
        {
            return IsDeclared(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Determine if a symbol has been declared in the local environment
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of the symbol</param>
        /// True if the symbol is either delcared or bound in the local environment,
        /// false otherwise
        public bool IsDeclared(string package, string name)
        {
            return IsDeclared(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Determine if a symbol has been declared in the local environment
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <returns>
        /// True if the symbol is either delcared or bound in the local environment,
        /// false otherwise
        /// </returns>
        public bool IsDeclared(Symbol symbol)
        {
            lock (symboltable)
            {
                return symboltable.ContainsKey(symbol);
            }
        }


        /// <summary>
        /// Sets a symbol's property value
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="prop">The property</param>
        /// <param name="value">The value</param>
        public Object SetProperty(Symbol symbol, Symbol prop, Object value)
        {
            // Find the top-most environment
            Environment env = this;
            while (env.previousEnvironment != null)
                env = previousEnvironment;

            bool containsKey;

            if (value != null)
            {
                lock (env.symbolprops)
                {
                    containsKey = env.symbolprops.ContainsKey(symbol);
                }

                if (!containsKey)
                {
                    lock (env.symbolprops)
                    {
                        env.symbolprops[symbol] = new Dictionary<Symbol, object>();
                    }
                }

                lock (env.symbolprops[symbol])
                {
                    env.symbolprops[symbol][prop] = value;
                }
            }
            else
            {
                lock (env.symbolprops)
                {
                    containsKey = env.symbolprops.ContainsKey(symbol);
                }

                if (containsKey)
                {
                    lock (env.symbolprops[symbol])
                    {
                        containsKey = env.symbolprops[symbol].ContainsKey(prop);
                    }

                    if (containsKey)
                    {
                        lock (env.symbolprops[symbol])
                        {
                            env.symbolprops[symbol].Remove(prop);
                        }
                    }
                }
            }

            return value;
        }


        /// <summary>
        /// Returns the type of the specified symbol
        /// </summary>
        /// <param name="symbol">The name of the symbol</param>
        /// <returns>The decalared type of the symbol, or null if the symbol was not declared</returns>
        public Type TypeOf(string symbol)
        {
            return TypeOf(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Returns the type of the specified symbol
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="symbol">The name of the symbol</param>
        /// <returns>The decalared type of the symbol, or null if the symbol was not declared</returns>
        public Type TypeOf(string package, string symbol)
        {
            return TypeOf(Symbol.FromName(package, symbol));
        }

        /// <summary>
        /// Returns the type of the specified symbol
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <returns>The decalared type of the symbol, or null if the symbol was not declared</returns>
        public Type TypeOf(Symbol symbol)
        {
            Environment env = GetEnvironment(symbol, false);

            if (env == null)
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                            "TypeOf: {0} is not bound", symbol));

            lock (env.symboltypes)
            {
                return env.symboltypes.ContainsKey(symbol) ? env.symboltypes[symbol] : null;
            }
        }


        /// <summary>
        /// Unbind a symbol from the environment
        /// </summary>
        /// <param name="symbol">The name of the symbol to unbind</param>
        /// <returns>The symbol</returns>
        public object Unbind(string symbol)
        {
            return Unbind(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Unbind a symbol from the environment
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of the symbol</param>
        /// <returns>The symbol</returns>
        public object Unbind(string package, string name)
        {
            return Unbind(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Unbind a symbol from the environment
        /// </summary>
        /// <param name="symbol">The symbol to unbind</param>
        /// <returns>the symbol</returns>
        public object Unbind(Symbol symbol)
        {
            Environment environment = GetEnvironment(symbol, false);
            return (environment != null) ? environment.UnbindLocal(symbol) : symbol;
        }


        /// <summary>
        /// Unbind a symbol from the local environment
        /// </summary>
        /// <param name="symbol">The name of the symbol to unbind</param>
        /// <returns>The symbol</returns>
        public object UnbindLocal(string symbol)
        {
            return UnbindLocal(Symbol.FromName(symbol));
        }

        /// <summary>
        /// Unbind a symbol from the local environment
        /// </summary>
        /// <param name="package">The name of the package in which to find the symbol</param>
        /// <param name="name">The name of the symbol to unbind</param>
        /// <returns>The symbol</returns>
        public object UnbindLocal(string package, string name)
        {
            return UnbindLocal(Symbol.FromName(package, name));
        }

        /// <summary>
        /// Unbind a symbol from the local environment
        /// </summary>
        /// <param name="symbol">The symbol to unbind</param>
        /// <returns>The symbol</returns>
        public object UnbindLocal(Symbol symbol)
        {
            if (symbol == null)
                throw new YarrException("UnbindLocal: argument is null");

            bool containsKey;
            lock (symboltable)
            {
                containsKey = symboltable.ContainsKey(symbol);
            }

            if (!containsKey)
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                            "Unbind: {0} is not bound", symbol.ToString()));

            string symName = symbol.Name;
            if (symName[0] == '*' && symName[symName.Length - 1] == '*')
                throw new YarrException("Unbind: Cannot unbind system symbols");

            lock (symboltypes)
            {
                containsKey = symboltypes.ContainsKey(symbol);
            }

            if (containsKey)
            {
                lock (symboltypes)
                {
                    symboltypes.Remove(symbol);
                }
            }

            lock (symbolprops)
            {
                containsKey = symbolprops.ContainsKey(symbol);
            }

            if (containsKey)
            {
                lock (symbolprops)
                {
                    symbolprops.Remove(symbol);
                }
            }

            lock (symboltable)
            {
                symboltable.Remove(symbol);
            }

            return symbol;
        }
        #endregion


        #region Helper methods
        private object Assign(Symbol symbol, object value)
        {
            return GetEnvironment(symbol, true).AssignLocal(symbol, value);
        }


        internal void BindSetf(Symbol symbol, Closure f)
        {
            SetProperty(symbol, Symbol.SETF_DISP, f);
        }


        private Closure GetClosure(Symbol symbol)
        {
            Closure cl = (Closure)GetValue(symbol);
            return cl;
        }


        private Environment GetEnvironment(Symbol symbol, bool forSetf) 
		{
            bool containsKey;
            lock (symboltable)
            {
                containsKey = symboltable.ContainsKey(symbol);
            }

			if (containsKey)
				return this;

			if (previousEnvironment == null)
				return forSetf ? this : null;

            return previousEnvironment.GetEnvironment(symbol, forSetf);
		}


        internal Closure GetSetf (Symbol symbol)
        {
            return (Closure)GetProperty(symbol, Symbol.SETF_DISP);
        }
        #endregion
	}
}
